package away3d.core.math ;


import flash.geom.Vector;
import flash.geom.Vector3D;
import flash.geom.Matrix3D;



/**
 * Matrix3DUtils provides additional Matrix3D math functions.
 */
class Matrix3DUtils
{
    
    
    
    //TODO: need to check this
    /**
     * A reference to a Vector to be used as a temporary raw data container, to prevent object creation.
     */
    public static inline var RAW_DATA_CONTAINER : Vector<Float> = new Vector( 16 ) ;
    
    /**
     * Fills the 3d matrix object with values representing the transformation made by the given quaternion.
     * 
     * @param    quarternion    The quarterion object to convert.
     */
    public static function quaternion2matrix( quarternion: Quaternion, ?m : Matrix3D = null ) : Matrix3D
    {
        
        var x       = quarternion.x ;
        var y       = quarternion.y ;
        var z       = quarternion.z ;
        var w       = quarternion.w ;
        
        var xx      = x * x ;
        var xy      = x * y ;
        var xz      = x * z ;
        var xw      = x * w ;
        
        var yy      = y * y ;
        var yz      = y * z ;
        var yw      = y * w ;
        
        var zz      = z * z ;
        var zw      = z * w ;
        
        var raw     = RAW_DATA_CONTAINER ;
        raw[ 0  ]   = 1 - 2 * ( yy + zz ) ;
        raw[ 1  ]   = 2 * ( xy + zw ) ; 
        raw[ 2  ]   = 2 * ( xz - yw ) ; 
        raw[ 4  ]   = 2 * ( xy - zw ) ;
        raw[ 5  ]   = 1 - 2 * ( xx + zz ) ; 
        raw[ 6  ]   = 2 * ( yz + xw ) ;
        raw[ 8  ]   = 2 * ( xz + yw ) ; 
        raw[ 9  ]   = 2 * ( yz - xw ) ;
        raw[ 10 ]   = 1 - 2 * ( xx + yy ) ;
        raw[ 3 ]    = 0 ;
        raw[ 7 ]    = 0 ;
        raw[ 11 ]   = 0 ;
        raw[ 12 ]   = 0 ;
        raw[ 13 ]   = 0 ;
        raw[ 14 ]   = 0 ;
        raw[ 15 ]   = 1 ;
        
        if( m )
        {
            
            m.copyRawDataFrom( raw ) ;
            return m ;
            
        }
        
        return new Matrix3D( raw ) ;
        
    }
    
    
    /**
     * Returns a normalised <code>Vector3D</code> object representing the forward vector of the given matrix.
     * @param    m        The Matrix3D object to use to get the forward vector
     * @param    v         [optional] A vector holder to prevent make new Vector3D instance if already exists. Default is null.
     * @return            The forward vector
     */
    public static function getForward( m: Matrix3D, ?v: Vector3D = null ) : Vector3D
    {
        
        if( v == null )
        {
            
            v = new Vector3D( 0.0, 0.0, 0.0 ) ;
            
        }
        
        m.copyRowTo( 2, v ) ;
        v.normalize() ;
        
        return v ;
        
    }
    
    
    /**
     * Returns a normalised <code>Vector3D</code> object representing the up vector of the given matrix.
     * @param    m        The Matrix3D object to use to get the up vector
     * @param    v         [optional] A vector holder to prevent make new Vector3D instance if already exists. Default is null.
     * @return            The up vector
     */
    public static function getUp( m:Matrix3D, v:Vector3D = null ):Vector3D
    {
        
        if( v == null )
        {
            
            v ||= new Vector3D( 0.0, 0.0, 0.0 ) ;
            
        }
        
        m.copyRowTo( 1, v ) ;
        v.normalize() ;
        
        return v ;
    }
    
    
    /**
     * Returns a normalised <code>Vector3D</code> object representing the right vector of the given matrix.
     * @param    m        The Matrix3D object to use to get the right vector
     * @param    v         [optional] A vector holder to prevent make new Vector3D instance if already exists. Default is null.
     * @return            The right vector
     */
    public static function getRight( m:Matrix3D, v:Vector3D = null ):Vector3D
    {
        
        if( v == null )
        {
            
            v ||= new Vector3D( 0.0, 0.0, 0.0 ) ;
            
        }
        
        m.copyRowTo( 0, v ) ;
        v.normalize() ;
        
        return v ;
        
    }
    
    
    /**
     * Returns a boolean value representing whether there is any significant difference between the two given 3d matrices.
     */
    public static function compare( m1:Matrix3D, m2:Matrix3D ):Bool
    {
    
        var r1  = Matrix3DUtils.RAW_DATA_CONTAINER ;
        var r2  = m2.rawData ;
        
        m1.copyRawDataTo( r1 ) ;
        
        for ( i in 0...16 )
        {
            
            if( r1[ i ] != r2[ i ] )    return false ;
            
        }
        
        return true ;
    }

    public static function lookAt( matrix : Matrix3D, pos : Vector3D, dir : Vector3D, up : Vector3D )
    {
        
        var dirN    : Vector3D ;
        var upN     : Vector3D ;
        var lftN    : Vector3D ;
        var raw     = RAW_DATA_CONTAINER ;
        
        lftN        = dir.crossProduct( up ) ;
        lftN.normalize() ;
        
        upN         = lftN.crossProduct( dir ) ;
        upN.normalize() ;
        dirN        = dir.clone() ;
        dirN.normalize() ;
        
        raw[ 0 ]    = lftN.x ;
        raw[ 1 ]    = upN.x ;
        raw[ 2 ]    = -dirN.x ;
        raw[ 3 ]    = 0.0 ;
        
        raw[ 4 ]    = lftN.y ;
        raw[ 5 ]    = upN.y ;
        raw[ 6 ]    = -dirN.y ;
        raw[ 7 ]    = 0.0 ;
        
        raw[ 8 ]    = lftN.z ;
        raw[ 9 ]    = upN.z ;
        raw[ 10 ]   = -dirN.z ;
        raw[ 11 ]   = 0.0 ;
        
        raw[ 12 ]   = -lftN.dotProduct( pos ) ;
        raw[ 13 ]   = -upN.dotProduct( pos ) ;
        raw[ 14 ]   = dirN.dotProduct( pos ) ;
        raw[ 15 ]   = 1.0 ;
        
        matrix.copyRawDataFrom( raw ) ;
        
    }
    
    
}
